import frappe
from frappe import _
from frappe.utils import flt, cint
from erpnext.manufacturing.doctype.job_card.job_card import JobCard
from frappe.model.mapper import get_mapped_doc
from frappe.model.naming import get_default_naming_series
from frappe.query_builder.functions import IfNull, Max, Min

class CustomJobCard(JobCard):
	@property
	def custom_sent_qty(self):
		"""获取委外发出数量"""
		if not self.operation_id:
			return 0
			
		sent_qty = frappe.db.get_value(
			"Work Order Operation",
			self.operation_id,
			"custom_sent_qty"
		) or 0
		return sent_qty
	
	@property
	def custom_received_qty(self):
		"""获取委外入库数量"""
		if not self.operation_id:
			return 0
			
		received_qty = frappe.db.get_value(
			"Work Order Operation",
			self.operation_id,
			"custom_received_qty"
		) or 0
		return received_qty
	
	@property
	def custom_scrapped_qty(self):
		"""获取委外废品数量"""
		if not self.operation_id:
			return 0
			
		scrapped_qty = frappe.db.get_value(
			"Work Order Operation",
			self.operation_id,
			"custom_scrapped_qty"
		) or 0
		return scrapped_qty
	
	@property
	def custom_wip_qty(self):
		"""获取在制品入库数量"""
		# 从已提交且加工单号为当前加工单的在制品移转单中获取在制品入库数量，是用 db.sql 查询
		wip_qty = frappe.db.sql("""
			SELECT SUM(qty) FROM `tabWip Transfer Item`
			WHERE job_card = %s
			AND parent IN (
				SELECT name FROM `tabWip Transfer`
				WHERE docstatus = 1 AND type = 'Receive from Workshop'
			)
		""", (self.name,), as_dict=1)
		return wip_qty[0].qty if wip_qty else 0

	def get_current_operation_data(self):
		return frappe.get_all(
			"Job Card",
			fields=[
				"sum(total_time_in_mins) as time_in_mins",
				"sum(total_completed_qty) as completed_qty",
				"sum(process_loss_qty) as process_loss_qty",
				"sum(custom_actual_labour_cost) as custom_actual_labour_cost",
			],
			filters={
				"docstatus": 1,
				"work_order": self.work_order,
				"operation_id": self.operation_id,
				"is_corrective_job_card": 0,
			},
		)
	
	def update_work_order(self):
		if not self.work_order:
			return

		if self.is_corrective_job_card and not cint(
			frappe.db.get_single_value(
				"Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
			)
		):
			return

		for_quantity, time_in_mins, process_loss_qty = 0, 0, 0
		_from_time_list, _to_time_list = [], []

		data = self.get_current_operation_data()
		if data and len(data) > 0:
			for_quantity = flt(data[0].completed_qty)
			time_in_mins = flt(data[0].time_in_mins)
			process_loss_qty = flt(data[0].process_loss_qty)
			custom_actual_labour_cost = flt(data[0].custom_actual_labour_cost)

		wo = frappe.get_doc("Work Order", self.work_order)

		if self.is_corrective_job_card:
			self.update_corrective_in_work_order(wo)

		elif self.operation_id:
			self.validate_produced_quantity(for_quantity, process_loss_qty, wo)
			self.update_work_order_data(for_quantity, process_loss_qty, time_in_mins, wo, custom_actual_labour_cost)

	def update_work_order_data(self, for_quantity, process_loss_qty, time_in_mins, wo, custom_actual_labour_cost):
		workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate")
		jc = frappe.qb.DocType("Job Card")
		jctl = frappe.qb.DocType("Job Card Time Log")

		time_data = (
			frappe.qb.from_(jc)
			.from_(jctl)
			.select(Min(jctl.from_time).as_("start_time"), Max(jctl.to_time).as_("end_time"))
			.where(
				(jctl.parent == jc.name)
				& (jc.work_order == self.work_order)
				& (jc.operation_id == self.operation_id)
				& (jc.docstatus == 1)
				& (IfNull(jc.is_corrective_job_card, 0) == 0)
			)
		).run(as_dict=True)

		for data in wo.operations:
			if data.get("name") == self.operation_id:
				data.completed_qty = for_quantity
				data.process_loss_qty = process_loss_qty
				data.actual_operation_time = time_in_mins
				data.custom_actual_labour_cost = custom_actual_labour_cost
				data.actual_start_time = time_data[0].start_time if time_data else None
				data.actual_end_time = time_data[0].end_time if time_data else None
				if data.get("workstation") != self.workstation:
					# workstations can change in a job card
					data.workstation = self.workstation
					data.hour_rate = flt(workstation_hour_rate)

		wo.flags.ignore_validate_update_after_submit = True
		wo.update_operation_status()
		wo.calculate_operating_cost()
		wo.set_actual_dates()
		wo.save()
	
	@frappe.whitelist()
	def update_operation_price(self, force_save=False):
		"""Update operation price for the job card"""
		if not self.work_order:
			frappe.throw(_("Work Order not found"))
		
		# Get all fields from job card
		operation_data = {}
		for field in self.meta.fields:
			if not field.fieldname.startswith('_'):  # Skip internal fields
				value = getattr(self, field.fieldname, None)
				if value is not None and not callable(value):  # Skip None values and methods
					operation_data[field.fieldname] = value
		
		# Get latest price
		from light_mes.light_mes.doctype.operation_price_strategy.operation_price_strategy import get_operation_price_for_bom
		new_price = get_operation_price_for_bom(self.production_item, operation_data)
		
		if flt(new_price) != flt(self.custom_service_price):
			# Update job card price
			self.custom_service_price = new_price
			if self.custom_rate_type == "By Hour":
				# 计时：成本 = 单价 * (实际工时分钟 / 60)
				actual_time = self.total_time_in_mins or 0
				self.custom_actual_labour_cost = flt(
					self.custom_service_price * actual_time / 60,
					self.precision("custom_actual_labour_cost")
				)
			elif self.custom_rate_type == "By Qty":
				# 计件：成本 = 单价 * 完成数量
				completed_qty = self.get("service_price_base_qty") or self.total_completed_qty or 0
				self.custom_actual_labour_cost = flt(
					self.custom_service_price * completed_qty,
					self.precision("custom_actual_labour_cost")
       			)
			if force_save:
				self.flags.ignore_validate_update_after_submit = True
				self.save()
			
			# If job card is submitted, update work order operation price
			if self.docstatus == 1:
				self.update_work_order()
				
			return True
		return False

@frappe.whitelist()
def make_wip_transfer(source_name, target_doc=None):
	"""创建从加工单到在制品移转单的映射"""
	
	# 从 frappe.flags.args 中获取 transfer_type
	transfer_type = None
	if hasattr(frappe.flags, 'args') and frappe.flags.args:
		transfer_type = frappe.flags.args.get('transfer_type')
	
	# 根据不同的转移类型设置不同的验证条件
	validation = {}
	if transfer_type == "Receive from Workshop":
		# 在制品入库需要已提交的加工单
		validation = {"docstatus": ["=", 1]}
	elif transfer_type == "Send to Supplier" or transfer_type == "Receive from Supplier":
		# 委外发料和委外入库只需要草稿状态
		validation = {"docstatus": ["=", 0]}
	
	# 定义设置缺失值的函数
	def set_missing_values(source, target):
		# 设置单据编号模板
		target.naming_series = get_default_naming_series("Wip Transfer")
		# 设置移转类型
		target.type = transfer_type
	
	doclist = get_mapped_doc(
		"Job Card", 
		source_name, 
		{
			"Job Card": {
				"doctype": "Wip Transfer",
				"field_map": {
					"custom_plant_floor": "plant_floor",
					"custom_supplier": "from_supplier",
				},
				"validation": validation
			}
		}, 
		target_doc,
		set_missing_values  # 使用回调函数设置缺失值
	)
	
	# 添加一个移转项
	wip_item = frappe.new_doc("Wip Transfer Item")
	wip_item.job_card = source_name
	wip_item.work_order = frappe.db.get_value("Job Card", source_name, "work_order")
	wip_item.operation = frappe.db.get_value("Job Card", source_name, "operation")
	
	# 获取加工单信息
	job_card = frappe.get_doc("Job Card", source_name)
	work_order = frappe.get_doc("Work Order", job_card.work_order)
	
	# 根据移转类型设置不同的数量和仓库
	if transfer_type == "Receive from Workshop":
		# 车间入库 - 使用加工单的完工数量和报废数量
		wip_item.qty = job_card.total_completed_qty
		wip_item.scrap_qty = job_card.process_loss_qty
		wip_item.total_qty = job_card.total_completed_qty + job_card.process_loss_qty
	
	elif transfer_type == "Send to Supplier":
		# 委外发料 - 使用上一道工序的完工数量
		
		# 获取当前工序在工单中的位置
		current_operation_idx = 1
		
		for op in work_order.operations:
			if op.operation == job_card.operation:
				current_operation_idx = op.idx
				break
		
		# 如果不是第一道工序，获取上一道工序的完工数量
		if current_operation_idx > 1:
			prev_operation = work_order.operations[current_operation_idx - 2]
			
			# 直接从工序明细中获取在制品入库数量
			prev_completed_qty = prev_operation.custom_wip_qty or 0
			
			if prev_completed_qty > 0:
				wip_item.qty = prev_completed_qty
				wip_item.total_qty = prev_completed_qty
			else:
				# 如果上一道工序没有完工数量，报错提示
				frappe.throw(_("The previous operation has not completed, please complete the previous operation first."))
		else:
			# 如果是第一道工序，提示第一道工序不能进行委外发料
			frappe.throw(_("The first operation cannot be sent to a supplier."))
	
	elif transfer_type == "Receive from Supplier":
		# 委外入库 - 使用之前委外发料单的数量
		
		# 使用正确的方式查询子表
		# 方法1：使用SQL直接查询
		issue_transfers = frappe.db.sql("""
			SELECT parent FROM `tabWip Transfer Item`
			WHERE job_card = %s
			AND parent IN (
				SELECT name FROM `tabWip Transfer`
				WHERE docstatus = 1 AND type = 'Send to Supplier'
			)
		""", (source_name,), as_dict=1)
		
		if issue_transfers:
			# 获取最新的委外发料单
			latest_transfer = frappe.get_doc("Wip Transfer", issue_transfers[0].parent)
			
			# 查找对应的明细行
			for item in latest_transfer.wip_transfer_item:
				if item.job_card == source_name:
					# 考虑退货数量
					wip_item.qty = item.qty - item.returned_qty
					break
			else:
				# 如果没有找到对应的明细行，报错提示
				frappe.throw(_("No issue transfer found for the job card."))
		else:
			# 如果没有找到委外发料单，报错提示
			frappe.throw(_("No issue transfer found for the job card."))
		
		# 设置入库数量等于数量（没有废品）
		wip_item.total_qty = wip_item.qty
	
	doclist.append("wip_transfer_item", wip_item)
	
	return doclist

@frappe.whitelist()
def bulk_update_operation_price(names):
	"""Bulk update operation prices for job cards
	
	Args:
		names: Comma separated job card names
	"""
	if not names:
		return
		
	names = names.split(",")
	updated_count = 0
	
	for name in names:
		try:
			job_card = frappe.get_doc("Job Card", name)
			if job_card.update_operation_price(force_save=True):
				updated_count += 1
		except Exception as e:
			frappe.log_error(f"Failed to update price for job card {name}: {str(e)}")
			continue
			
	if updated_count > 0:
		frappe.msgprint(_("{0} job cards have been updated").format(updated_count))
	else:
		frappe.msgprint(_("No job cards need to be updated"))

def get_default_naming_series(doctype):
	"""获取文档类型的默认命名系列，考虑用户自定义设置"""
	# 首先检查是否有用户自定义的默认值
	property_setter = frappe.db.get_value(
		"Property Setter",
		{
			"doc_type": doctype,
			"field_name": "naming_series",
			"property": "default"
		},
		"value"
	)
	
	if property_setter:
		return property_setter

	return frappe.model.naming.get_default_naming_series(doctype)